# LyricsDownloader - Search and download lyrics from the internet.
#
# Copyright (C) 2007 Lauri Taimila
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2007, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import re
import urllib
import threading
from xml.dom import minidom
from pysqlite2 import dbapi2 as sqlite

class LyricsDownloader(threading.Thread):
    """
    Search and download song lyrics from the internet. 
    Update music cache if lyrics found.
    
    This class is heavily based on Rhythmbox - Song lyrics plugin. That plugin
    is excelent work of Eduardo Gonzalez and Jonathan Matthew. Song lyrics plugin
    is released under GPLv2 (or higher).
    
    Copyrights of the plugin:
        Copyright 2005 Eduardo Gonzalez
        Copyright (C) 2006 Jonathan Matthew
    
    See more: http://www.gnome.org/projects/rhythmbox/
    
    If you want better lyrics search, please contribute to Rhyhtmbox project.
    """
    
    # Strip (Live in New York) and stuff like that from song title before search
    LYRIC_TITLE_STRIP = ["\(live[^\)]*\)", "\(acoustic[^\)]*\)", "\([^\)]*mix\)", "\([^\)]*version\)", "\([^\)]*edit\)", "\(feat[^\)]*\)"]
    
    # Replace some characters in song title
    LYRIC_TITLE_REPLACE=[("/", "-"), (" & ", " and ")]
    
    # Replace some characters in artist name
    LYRIC_ARTIST_REPLACE=[("/", "-"), (" & ", " and ")]
    
    def __init__(self, title, artist, filename, cache):
        """Initialize lyrics downloader."""
        threading.Thread.__init__(self)
        self.setName("LyricsDownloader")
        self.title = title.lower()      # Track title
        self.artist = artist.lower()    # Artist name
        self.filename = filename      # Filename (we search lyrics for this audio file)
        self.cache = cache              # Cache file
        
    def run(self):
        """Start searching and downloading lyrics"""
        try:
            search_results_XML = self._getSearchResultXML()
            lyrics_XML = self._getLyricsXML(search_results_XML)
            lyrics = self._parseLyricsXML(lyrics_XML)
            self._saveLyrics(lyrics)
    	except:
    	    pass
    	
    def __getSearchResultXML(self):
        """Make a search to Leoslyrics.com and return results XML."""
        # replace ampersands and the like
    	for exp in self.__class__.LYRIC_ARTIST_REPLACE:
    		p = re.compile(exp[0])
    		artist = p.sub(exp[1], self.artist)
    	for exp in self.__class__.LYRIC_TITLE_REPLACE:
    		p = re.compile(exp[0])
    		title = p.sub(exp[1], self.title)

        # strip things like "(live at Somewhere)", "(accoustic)", etc
        for exp in self.__class__.LYRIC_TITLE_STRIP:
            p = re.compile (exp)
            title = p.sub ('', title)

    	# Clean spaces
    	title = title.strip()
    	artist = artist.strip()
    	
    	# Search string for Leoslyrics.com API
    	url = "http://api.leoslyrics.com/api_search.php?auth=Entertainer&artist=%s&songtitle=%s" % (
                                                        		urllib.quote(artist.encode('utf-8')),
                                                        		urllib.quote(title.encode('utf-8')))  
        temp = urllib.urlopen(url)
        search_results = temp.read()
        return search_results
    
    def _getLyricsXML(self, search_results):
        """Download lyrics XML-file based on search results XML."""
        if search_results is None:
            raise Exception("Can't get lyrics for this track.")
        
        # Parse results
        try:
    	    xmldoc = minidom.parseString(search_results).documentElement
    	except:
    	    raise Exception("Can't get lyrics for this track.")
    	
    	# Result code. 0 is SUCCESS    
    	result_code = xmldoc.getElementsByTagName('response')[0].getAttribute('code')
    	if result_code != '0':
    	    #FIXME: We should try again in 5-10 minutes?
    	    xmldoc.unlink()
    	    raise Exception("Can't get lyrics for this track.")

        # Limit to top 5 (we don't need top 100 really)
    	matches = xmldoc.getElementsByTagName('result')[:5]
    	hids = map(lambda x: x.getAttribute('hid'), matches)
    	
    	# No matches
    	if len(hids) == 0:
    	    xmldoc.unlink()
    	    raise Exception("Can't get lyrics for this track.")
    	    
    	xmldoc.unlink()
    	
    	# URL for found lyrics
    	lyrics_xml = None
    	try: #FIXME: Change auth from Rhythmbox to Entertainer
            url = "http://api.leoslyrics.com/api_lyrics.php?auth=Rhythmbox&hid=%s" % (urllib.quote(hids[0].encode('utf-8')))
            temp = urllib.urlopen(url)
            lyrics_xml = temp.read()
        except:
            pass
        return lyrics_xml
    
    def _parseLyricsXML(self, lyrics_xml):
        """Parse lyrics XML and return lyrics string"""
        if lyrics_xml is None:
    	    raise Exception("Can't get lyrics for this track.")
    	try:
    	    xmldoc = minidom.parseString(lyrics_xml).documentElement
    	except:
    	    raise Exception("Can't get lyrics for this track.")
        
        # Parse XML string
    	lyrics = xmldoc.getElementsByTagName('title')[0].firstChild.nodeValue
    	lyrics += ' - ' + xmldoc.getElementsByTagName('artist')[0].getElementsByTagName('name')[0].firstChild.nodeValue + '\n\n'
    	lyrics += xmldoc.getElementsByTagName('text')[0].firstChild.nodeValue
    	xmldoc.unlink()

    	lyrics += "\n\n"+ "Lyrics provided by leoslyrics.com"
        return lyrics.encode("utf-8")
        
    def _saveLyrics(self, lyrics):
        """Save lyrics to the music cache database."""
        
        escaped_lyrics = lyrics.replace('\'','\'') #FIXME: Is there a better way for this?
        
        db_conn = sqlite.connect(self.cache)
        db_cur = db_conn.cursor()
        db_cur.execute("""UPDATE track SET lyrics="%s" WHERE filename="%s" """ % (lyrics, self.filename))
        db_conn.commit()
        db_conn.close()
        
